/**
 * @file bb_i2c_master.c
 * @author Zhai Tao (zhaitao.as@outlook.com)
 * @brief
 * @version 0.1
 * @date 2022-03-03
 *
 * @copyright zhaitao.as@outlook.com (c) 2022
 *
 */

#include "bb_i2c_master.h"
#include <stdio.h>

#define I2C_DELAY i2c->init_obj->_delay_us(i2c->half_clk)
#define I2C_SET_SDA i2c->init_obj->sda_set(1)
#define I2C_CLEAR_SDA i2c->init_obj->sda_set(0)
#define I2C_SET_SCL i2c->init_obj->scl_set(1)
#define I2C_CLEAR_SCL i2c->init_obj->scl_set(0)

#define SDA_READ i2c->init_obj->sda_get()
#define SCL_READ i2c->init_obj->scl_get()
#define SDA_IN i2c->init_obj->sda_dir(0)
#define SDA_OUT i2c->init_obj->sda_dir(1)
#define SCL_IN i2c->init_obj->scl_dir(0)
#define SCL_OUT i2c->init_obj->scl_dir(1)

#define true 1
#define false 0


/*
		***** Private Fuctions *****
*/

static inline void I2C_start_cond(struct bb_i2c_master *i2c)
{
	I2C_SET_SCL;
	I2C_SET_SDA;
	I2C_DELAY;
	I2C_CLEAR_SDA;
	I2C_DELAY;
	I2C_CLEAR_SCL;
	I2C_DELAY;
}

static inline void I2C_stop_cond(struct bb_i2c_master *i2c)
{
	I2C_CLEAR_SDA;
	I2C_DELAY;
	I2C_SET_SCL;
	I2C_DELAY;
	I2C_SET_SDA;
	I2C_DELAY;
}

static inline void I2C_write_bit(struct bb_i2c_master *i2c, BB_U8 b)
{
	if (b > 0)
		I2C_SET_SDA;
	else
		I2C_CLEAR_SDA;

	I2C_DELAY;
	I2C_SET_SCL;
	I2C_DELAY;
	I2C_CLEAR_SCL;
}

static inline BB_U8 I2C_read_SDA(struct bb_i2c_master *i2c)
{
	return i2c->init_obj->sda_get();
}

static inline BB_U8 I2C_read_bit(struct bb_i2c_master *i2c)
{
	BB_U8 b;

	I2C_SET_SDA;
	I2C_DELAY;
	I2C_SET_SCL;
	I2C_DELAY;

	// b = I2C_read_SDA();
	b = i2c->init_obj->sda_get();

	I2C_CLEAR_SCL;

	return b;
}

static inline _Bool I2C_write_byte(struct bb_i2c_master *i2c, BB_U8 B, _Bool start, _Bool stop)
{
	BB_U8 ack = 0;

	if (start)
		I2C_start_cond(i2c);

	BB_U8 i;
	for (i = 0; i < 8; i++) {
		I2C_write_bit(i2c, B & 0x80); // write the most-significant bit
		B <<= 1;
	}

	ack = I2C_read_bit(i2c);

	if (stop)
		I2C_stop_cond(i2c);

	return !ack; // 0-ack, 1-nack
}

// Reading a byte with I2C:
static inline BB_U8 I2C_read_byte(struct bb_i2c_master *i2c, BB_BOOL ack, BB_BOOL stop)
{
	BB_U8 B = 0;

	BB_U8 i;
	for (i = 0; i < 8; i++) {
		B <<= 1;
		B |= I2C_read_bit(i2c);
	}

	if (ack)
		I2C_write_bit(i2c, 0);
	else
		I2C_write_bit(i2c, 1);

	if (stop)
		I2C_stop_cond(i2c);

	return B;
}

void bb_i2c_7addr_to_8addr(BB_U16 addr_no_wr, BB_U16 *addr_w, BB_U16 *addr_r)
{
	*addr_w = (addr_no_wr << 1);
	*addr_r = (addr_no_wr << 1) + 1;
}
// Sending a byte with I2C:
static inline _Bool I2C_send_byte(struct bb_i2c_master *i2c, BB_U8 address, BB_U8 data)
{
	//    if( I2C_write_byte( address << 1, true, false ) )   // start, send address, write
	if (I2C_write_byte(i2c, address, true, false)) // start, send address, write
	{
		// send data, stop
		if (I2C_write_byte(i2c, data, false, true))
			return true;
	}

	I2C_stop_cond(i2c); // make sure to impose a stop if NAK'd
	return false;
}

// Receiving a byte with a I2C:
static inline BB_U8 I2C_receive_byte(struct bb_i2c_master *i2c, BB_U8 address)
{
	if (I2C_write_byte(i2c, (address << 1) | 0x01, true, false)) // start, send address, read
	{
		return I2C_read_byte(i2c, false, true);
	}

	return 0; // return zero if NAK'd
}

// Sending a byte of data with I2C:
static inline _Bool I2C_send_byte_data(struct bb_i2c_master *i2c, BB_U8 address, BB_U8 reg, BB_U8 data)
{
	//    if( I2C_write_byte( address << 1, true, false ) )   // start, send address, write
	if (I2C_write_byte(i2c, address, true, false)) {
		if (I2C_write_byte(i2c, reg, false, false)) // send desired register
		{
			if (I2C_write_byte(i2c, data, false, true))
				return true; // send data, stop
		}
	}

	I2C_stop_cond(i2c);
	return false;
}

// Receiving a byte of data with I2C:
static inline BB_U8 I2C_receive_byte_data(struct bb_i2c_master *i2c, BB_U8 address, BB_U8 reg)
{
	// if( I2C_write_byte( address << 1, true, false ) )   // start, send address, write
	if (I2C_write_byte(i2c, address, true, false)) {
		if (I2C_write_byte(i2c, reg, false, false)) // send desired register
		{
			if (I2C_write_byte(i2c, (address << 1) | 0x01, true, false)) // start again, send address, read
			{
				return I2C_read_byte(i2c, false, true); // read data
			}
		}
	}

	I2C_stop_cond(i2c);
	return 0; // return zero if NACKed
}

/*
		***** Public Fuctions *****
*/

void bb_i2c_master_initial(struct bb_i2c_master *i2c, struct bb_i2c_master_initial_typedef *init_obj)
{
	double cycle = 1000000.0 / init_obj->speed;
	i2c->half_clk = cycle / 2;

	i2c->init_obj = init_obj;

	I2C_stop_cond(i2c);
}

BB_BOOL bb_i2c_master_send(struct bb_i2c_master *i2c, BB_U16 address, BB_U8 data[], BB_U32 size)
{
	if (I2C_write_byte(i2c, address, true, false)) // first byte
	{
		for (int i = 0; i < size; i++) {
			if (i == size - 1) {
				if (I2C_write_byte(i2c, data[i], false, true))
					return true;
			} else {
				if (!I2C_write_byte(i2c, data[i], false, false))
					break; // last byte
			}
		}
	}

	I2C_stop_cond(i2c);
	return false;
}

BB_BOOL bb_i2c_master_recive(struct bb_i2c_master *i2c, BB_U16 dev_addr, BB_U8 reg[], BB_U8 reg_len, BB_U8 *buffer, BB_U32 len)
{
	if (I2C_write_byte(i2c, dev_addr, true, false)) {
		for (int i = 0; i < reg_len; i++) {
			if (!I2C_write_byte(i2c, reg[i], false, false))
				break;
		}
		if (I2C_write_byte(i2c, dev_addr | 0x01, true, false)) // start again, send address, read (LSB signifies R or W)
		{
			for (int j = 0; j < len; j++) {
				*buffer++ = I2C_read_byte(i2c, false, false); // read data
			}
			I2C_stop_cond(i2c);
			return true;
		}
	}
	I2C_stop_cond(i2c);
	return false;
}

void bb_i2c_slave_detect(struct bb_i2c_master *i2c)
{
	for(int i = 0; i < 128; i++){
		if (i%16 == 0) {
			printf("\r\n");
		}
		
		uint8_t slave_addr = i << 1;
		uint8_t dummy = 0;
		uint8_t ret = bb_i2c_master_send(i2c, slave_addr, &dummy, 1);
		if (ret) {
			printf("%02x ", i);
		} else {
			printf("-- ");
		}
		
	}
}